package common_requests;

#########################################################################
# The aim of this object is to perform common requests against a series	#
# of nodes.								#
# It will make use of communication objects when required, and also	#
# supports logging as well...						#
#########################################################################

use strict;
use clreq;
use Data::Dumper;
use cldb;
use File::Basename;
use Carp qw(cluck);

my $error_rc=0;
my $error_msg=undef;

sub logit {
my $self=shift;
my $type=shift;
my $cline;
my $xx;

	return if !defined($self->{logger});
	for $cline (@_) {
		my @F=split(/\n/,$cline);
		for $xx (@F) {
			$self->{logger}->msg($type,$xx);
		}
	}
}

sub msg {
my $self=shift;

	logit($self,"msg",@_);
}

sub warning {
my $self=shift;

	logit($self,"warn",@_);
}

sub error {
my $self=shift;
my $rc;

	if(scalar(@_)>1) {
		my $x=scalar(@_)-1;
		$rc=$_[$x];
		@_=@_[0 .. $x-1];
	}
	logit($self,"error",@_);
	exit($rc) if defined($rc);
}

#########################################################################
# Only thing passed in is clreqdxml object - everything else necessary	#
# can be easily got from config database.				#
# Args:									#
# clreqdxml	clreqdxml object to use for host information.		#
# [encryption]	Whether to use Key or Rsa based encryption.		#
# [logger]	Logger object for logging messages.			#
# [timeout]     Timeout for connections - default is 10.		#
# [parallel]	Spwan processes for each host or run sequentially.	#
#		Default is sequential. Change to 0 or 1.		#
#########################################################################

sub new {
my $self={};
my $class=shift;
my %args=@_;

	$error_rc=0;
	$error_msg=undef;

	#################################################################
	# Abort with high level error if missing mandatory arg.		#
	#################################################################

	if(!exists($args{clreqdxml})) {
		cluck("Missing required 'clreqdxml' argument.");
	}	
	$self->{clreqdxml}=$args{clreqdxml};
	if(!exists($args{encryption})) {
		$args{encryption}="rsa";
	} else {
		$args{encryption}=lc($args{encryption});
		if($args{encryption} ne "rsa" && $args{encryption} ne "key") {
			cluck("Encryption type must be 'rsa' or 'key'.");
		}
	}
	$self->{timeout}=10;
	$self->{timeout}=$args{timeout} if exists($args{timeout});
	if(exists($args{logger})) {
		$self->{logger}=$args{logger};
	} else {
		$self->{logger}=undef;
	}
	if(exists($args{parallel})) {
		$self->{parallel}=$args{parallel};
	} else {
		$self->{parallel}=0;
	}

	#################################################################
	# We generate a clreq object now that we can...			#
	#################################################################
	my %a=();
	$a{logger}=$self->{logger} if defined($self->{logger});
	$a{timeout}=$self->{timeout} if defined($self->{timeout});
	$a{encryption}=$self->{encryption} if defined($self->{encryption});
	$a{clreqdxml}=$self->{clreqdxml};
	$a{parallel}=$self->{parallel};
	my @hlist=$self->{clreqdxml}->get_host_list;
	$a{hosts}=\@hlist;
	my $b=new clreq(%a);
	if(!defined($b)) {
		my ($rc,$str)=clreq::get_error;
		cluck("Failed to create 'clreq' object [error=$str].");
	}
	$self->{clreq}=$b;
	$self->{ARGS}=[%a];
	bless($self,$class);
	return $self;
}

#########################################################################
# The get_common_free_port will return a series of ports that are	#
# currently free across all nodes (or all that return a response in	#
# the specified timeout).						#
#									#
# Arguments:								#
# count		Number of ports to return.				#
# force		Allow to run if one or more hosts not available		#
#									#
# Returns:								#
# (hashref,undef)		- Results as expected. Hash keys are	#
#				  node names. Might not be all node	#
#			 	  names.				#
# (undef,error)			- An error occured - error is a string	#
#				  indicating the problem.		#
#########################################################################

sub get_common_free_port {
my $self=shift;
my %args=@_;
my $ah;
my $count=1;
my $force=0;

	$force=$args{force} if exists($args{force});
	$count=$args{count} if exists($args{count});
	$ah=$self->{clreq}->run(cmd=>["get_free_plist"]);
	if(!defined($ah)) {
		return (undef,"No results returned from 'clreq' call.");
	}

	my @hl=$self->{clreqdxml}->get_host_list;
	cluck("No valid host list returned!") if ! @hl;

	#################################################################
	# We need to make sure all nodes have returned results, and 	#
	# none of the results are in error.				#
	#################################################################

	my $ch;
	for $ch (@hl) {
		if(!exists($ah->{$ch})) {
			return (undef,"Missing results from node '$ch'.");
		}
		my $r=$ah->{$ch};
		if($r =~ /ERROR No matching records/) {
			delete $ah->{$ch};
			next;
		}
		if($r =~ /^ERROR CONNECT ERROR:/) {
			delete $ah->{$ch};
			next if $force;
		}
		if($r !~ /^OK/) {
			return (undef,"Host '$ch' returned '$r'.");
		}
	}

	#################################################################
	# So all nodes returned some valid information, so we need to	#
	# break down each request which will be in X,X-Y,... format	#
	# and then return the first N ports that are common across	#
	# all nodes! Easiest way (but expensive in memory) is to 	#
	# generate hashes.						#
	#################################################################

	my %phash=();
	for $ch (@hl) {
		next if !exists($ah->{$ch});
		my @F=split(/\s+/,$ah->{$ch});
		@F=split(/,/,$F[1]);
		my $cps;
		for $cps (@F) {
			my ($first,$last);
			if($cps =~ /\-/) {
				($first,$last)=split(/\-/,$cps);
			} else {
				$first=$last=$cps;
			}
			while($first<=$last) {
				if(!exists($phash{$first})) {
					$phash{$first}=[];
				}
				push @{$phash{$first}},$ch;
				$first++;
			}
		}
	}

	#################################################################
	# So the phash is a hash for each port found, and each value	#
	# is a list of nodes that have that port available - so now	#
	# just return the first ones that have a list length of the	#
	# number of nodes.						#
	#################################################################

	my $x=scalar(keys(%$ah));
	my @p=();
	for (sort(keys(%phash))) {
		if(scalar(@{$phash{$_}})==$x) {
			push @p,$_;
			$count--;
			last if !$count;
		}
	}
	if($count) {
		return(undef,"Not enough free common ports.");
	}
	return (\@p,undef);
}

#########################################################################
# The run_config_sql will return the results of running a piece of SQL	#
# against the configuration database.					#
#									#
# Arguments:								#
# sql		The SQL to run.						#
# force		1 if OK to run if one or more servers down.		#
#									#
# Returns:								#
# (hashref,undef)		- Results as expected. Hash keys are	#
#				  node names. Might not be all node	#
#			 	  names.				#
# (undef,error)			- An error occured - error is a string	#
#				  indicating the problem.		#
#########################################################################

sub run_config_sql {
my $self=shift;
my %args=@_;
my $ah;
my $sql;
my $force=0;

	$force=$args{force} if exists($args{force});
	$sql=$args{sql} if exists($args{sql});
	$ah=$self->{clreq}->run(cmd=>["perform_adhoc_sql","DBTYPE","config","SQL",$sql]);
	if(!defined($ah)) {
		return (undef,"No results returned from 'clreq' call.");
	}

	my @hl=$self->{clreqdxml}->get_host_list;
	cluck("No valid host list returned!") if ! @hl;

	#################################################################
	# We need to make sure all nodes have returned results, and 	#
	# none of the results are in error.				#
	#################################################################

	my %rr=();
	my $ch;
	for $ch (@hl) {
		if(!exists($ah->{$ch})) {
			return (undef,"Missing results from node '$ch'.");
		}
		my $r=$ah->{$ch};
		if($r =~ /^FAIL /) {
			return(undef,"Host '$ch' returned '$r'.");
		}
		if($r =~ /ERROR No matching records/) {
			$rr{$ch}=undef;
			next;
		}
		if($r =~ /^ERROR CONNECT ERROR:/) {
			next if $force;
			return(undef,"Host '$ch' returned '$r'.");
		}
		$rr{$ch}=$r;
	}
	return (\%rr,undef);
}


# args: recreate  / force

sub mk_config_db {
my $self=shift;
my %args=@_;
my $ah;
my $force=0;
my $recreate=0;

	$force=$args{force} if exists($args{force});
	$recreate=$args{recreate} if exists($args{recreate});

	#################################################################
	# If recreate==1 then just force-create them...			#
	#################################################################

	my %rr=();
	my @hl=$self->{clreqdxml}->get_host_list;
	cluck("No valid host list returned!") if ! @hl;
	if($recreate) {
		$ah=$self->{clreq}->run(cmd=>["mk_config_db","FORCE","1"]);
		if(!defined($ah)) {
			return (undef,"No results returned from 'clreq' call.");
		}

		my $ch;
		for $ch (@hl) {
			if(!exists($ah->{$ch})) {
				return (undef,"Missing results from node '$ch'.");
			}
			my $r=$ah->{$ch};
			if($r =~ /^FAIL /) {
				return(undef,"Host '$ch' returned '$r'.");
			}
			$rr{$ch}=$r;
		}
		return (\%rr,undef);
	}

	#########################################################
	# recreate == 0, so check for hosts that do not yet	#
	# have a db and just create it on those.		#
	#########################################################

	#########################################################
	# We need to make sure all nodes have returned results, #
	# and none of the results are in error.			#
	#########################################################

	$ah=$self->{clreq}->run(cmd=>["does_db_exist"]);
	my @no=();
	my $ch;
	for $ch (@hl) {
		if(!exists($ah->{$ch})) {
			return (undef,"Missing results from node '$ch'.");
		}
		my $r=$ah->{$ch};
		if($r =~ /^yes/i) {
			$rr{$ch}="Exists";
			next;
		}
		push @no,$ch;
	}

	#########################################################
	# At this point no is a list of hosts on which the	#
	# database needs to be created...			#
	#########################################################
	my %aa=(@{$self->{ARGS}});
	$aa{hosts}=[@no];
	my $b=new clreq(%aa);
	$ah=$b->run(cmd=>["mk_config_db"]);

	for $ch (@no) {
		if(!exists($ah->{$ch})) {
			return (undef,"Missing results from node '$ch'.");
		}
		my $r=$ah->{$ch};
		if($r =~ /^FAIL /) {
			return(undef,"Host '$ch' returned '$r'.");
		}
		$rr{$ch}=$r;
	}

	return (\%rr,undef);
}

sub setup_config_globals {
my $self=shift;
my %args=@_;
my $ah;
my $force=0;
my $recreate=0;

	$force=$args{force} if exists($args{force});
	$recreate=$args{recreate} if exists($args{recreate});

	#################################################################
	# If recreate==1 then just force-create them...			#
	#################################################################

	my %rr=();
	my @hl=$self->{clreqdxml}->get_host_list;
	cluck("No valid host list returned!") if ! @hl;
	$ah=$self->{clreq}->run(cmd=>["mk_config_db","FORCE","$recreate"]);
	if(!defined($ah)) {
		return (undef,"No results returned from 'clreq' call.");
	}

	my $ch;
	for $ch (@hl) {
		if(!exists($ah->{$ch})) {
			return (undef,"Missing results from node '$ch'.");
		}
		my $r=$ah->{$ch};
		if($r =~ /^ERROR CONNECT ERROR:/) {
			delete $ah->{$ch};
			next if $force;
		}
		if($r =~ /^FAIL /) {
			return(undef,"Host '$ch' returned '$r'.");
		}
		$rr{$ch}=$r;
	}
	for (keys %rr) {
		if($rr{$_} =~ /^(ERROR|FAIL)/) {
			return (\%rr,undef);
		}
	}

	#################################################################
	# Now load the global settings...				#
	#################################################################

	$ah=$self->{clreq}->run(cmd=>["setup_config_globals","FORCE","$recreate"]);
	if(!defined($ah)) {
		return (undef,"No results returned from 'clreq' call.");
	}
	for $ch (@hl) {
		if(!exists($ah->{$ch})) {
			return (undef,"Missing results from node '$ch'.");
		}
		my $r=$ah->{$ch};
		if($r =~ /^ERROR CONNECT ERROR:/) {
			delete $ah->{$ch};
			next if $force;
		}
		if($r =~ /^FAIL /) {
			return(undef,"Host '$ch' returned '$r'.");
		}
		$rr{$ch}=$r;
	}
	for (keys %rr) {
		if($rr{$_} =~ /^(ERROR|FAIL)/) {
			return (\%rr,undef);
		}
	}
	return (\%rr,undef);
}

#########################################################################
# Attempt to load/reload all IP details for all available hosts.	#
#########################################################################

sub load_node_ips {
my $self=shift;
my %args=@_;
my $ah;
my $force=0;

	$force=$args{force} if exists($args{force});
	my %rr=();
	my @hl=$self->{clreqdxml}->get_host_list;
	cluck("No valid host list returned!") if ! @hl;
	$ah=$self->{clreq}->run(cmd=>["load_node_ips"]);
	if(!defined($ah)) {
		return (undef,"No results returned from 'clreq' call.");
	}

	my $ch;
	for $ch (@hl) {
		if(!exists($ah->{$ch})) {
			return (undef,"Missing results from node '$ch'.");
		}
		my $r=$ah->{$ch};
		if($r =~ /^ERROR CONNECT ERROR:/) {
			delete $ah->{$ch};
			next if $force;
			return(undef,"Unable to connect to host '$ch'.");
		}
		if($r =~ /^FAIL /) {
			return(undef,"Host '$ch' returned '$r'.");
		}
		$rr{$ch}=$r;
	}
	for (keys %rr) {
		if($rr{$_} =~ /^(ERROR|FAIL)/) {
			return (\%rr,undef);
		}
	}
	return (\%rr,undef);
}

#########################################################################
# Check number of records in the ports file and if empty perform a	#
# perform_port_scan action.						#
#########################################################################

sub perform_port_scan {
my $self=shift;
my %args=@_;
my $ah;
my $force=0;

	$force=$args{force} if exists($args{force});

	#################################################################
	# Work out which machines [at least those that respond] already	#
	# have entries in the ports tables and ignore those from the	#
	# list.								#
	#################################################################
	$ah=$self->{clreq}->run(cmd=>["perform_adhoc_sql","DBTYPE","config","SQL","select count(*) AS count FROM ports"]);
	if(!defined($ah)) {
		return (undef,"No results returned from 'clreq' call.");
	}
	my %rr=();
	my @hl=$self->{clreqdxml}->get_host_list;
	cluck("No valid host list returned!") if ! @hl;

	#################################################################
	# We need to make sure all nodes have returned results, and 	#
	# none of the results are in error.				#
	#################################################################

	my $ch;
	for $ch (@hl) {
		if(!exists($ah->{$ch})) {
			return (undef,"Missing results from node '$ch'.");
		}
		my $r=$ah->{$ch};
		if($r =~ /^FAIL /) {
			return(undef,"Host '$ch' returned '$r'.");
		}
		if($r =~ /ERROR No matching records/) {
			$rr{$ch}=undef;
			next;
		}
		if($r =~ /^ERROR CONNECT ERROR:/) {
			next if $force;
			return(undef,"Unable to connect to host '$ch'.");
		}
		my @F=split(/\n/,$r);
		if(!exists($F[1])) {
			next if $force;
			return(undef,"Invalid results from '$ch'.");
		}
		$rr{$ch}=$F[1];
	}
	# Generate a list of with 0 entries...
	my @nhl=();
	for (keys %rr) {
		if($rr{$_}==0) {
			push @nhl,$_;
			delete $rr{$_};
		}
	}

	#################################################################
	# Perform a port scan on nhl hosts...				#
	#################################################################
	my %aa=(@{$self->{ARGS}});
	$aa{hosts}=[@nhl];
	my $b=new clreq(%aa);
	$ah=$b->run(cmd=>["perform_port_scan"]);

	#################################################################
	# Check the results ...						#
	#################################################################

	for $ch (@nhl) {
		if(!exists($ah->{$ch})) {
			return (undef,"Missing results from node '$ch'.");
		}
		my $r=$ah->{$ch};
		if($r =~ /^FAIL /) {
			return(undef,"Host '$ch' returned '$r'.");
		}
		if($r =~ /^ERROR CONNECT ERROR:/) {
			next if $force;
			return(undef,"Unable to connect to host '$ch'.");
		}
		$rr{$ch}="OK";
	}
	return (\%rr,undef);
}

sub load_topology {
my $self=shift;
my %args=@_;
my $ah;
my $force=0;
my $recreate=0;

	$force=$args{force} if exists($args{force});
	$recreate=$args{recreate} if exists($args{recreate});
	my %rr=();
	my @hl=$self->{clreqdxml}->get_host_list;
	cluck("No valid host list returned!") if ! @hl;
	if($recreate) {
		$ah=$self->{clreq}->run(cmd=>["load_topology","RELOAD","1"]);
	} else {
		$ah=$self->{clreq}->run(cmd=>["load_topology"]);
	}
	if(!defined($ah)) {
		return (undef,"No results returned from 'clreq' call.");
	}

	my $ch;
	for $ch (@hl) {
		if(!exists($ah->{$ch})) {
			return (undef,"Missing results from node '$ch'.");
		}
		my $r=$ah->{$ch};
		if($r =~ /^ERROR CONNECT ERROR:/) {
			delete $ah->{$ch};
			next if $force;
			return(undef,"Unable to connect to host '$ch'.");
		}
		if($r =~ /^FAIL /) {
			return(undef,"Host '$ch' returned '$r'.");
		}
		$rr{$ch}=$r;
	}
	for (keys %rr) {
		if($rr{$_} =~ /^(ERROR|FAIL)/) {
			return (\%rr,undef);
		}
	}
	return (\%rr,undef);
}
1;
